Что такое const assertions в generic-функциях TypeScript?
as const) заставляет TypeScript выводить самый узкий литеральный и readonly-тип значения вместо расширенного базового типа. В generic-функциях это используют, чтобы дженерик выводил литеральные типы аргументов (например, точные строки или readonly-кортежи), а не обобщённые string/number[].Что такое const assertion
as const — это специальная форма type assertion, которая говорит компилятору: «выведи для этого значения самый узкий из возможных типов и сделай его неизменяемым». Без неё TypeScript расширяет (widening) типы литералов до базовых, потому что по умолчанию предполагает, что значение переменной может измениться.
let a = "admin"; // тип: string
let b = "admin" as const; // тип: "admin"
const obj = { role: "admin" }; // { role: string }
const obj2 = { role: "admin" } as const; // { readonly role: "admin" }
const arr = [1, 2, 3]; // number[]
const arr2 = [1, 2, 3] as const; // readonly [1, 2, 3]
Почему widening мешает в generic функциях
Generic-функция выводит тип параметра T на основе переданного аргумента. Если аргумент — литерал без as const, TypeScript расширит его тип ещё до вывода T, и вся точность потеряется.
function identity<T>(value: T): T {
return value;
}
const r1 = identity("admin"); // T = string
const r2 = identity("admin" as const); // T = "admin"
Это особенно важно для функций, которые строят кортежи, объекты-конфиги или API с точными строковыми литералами (например, роутинг, экшены редьюсера, enum-подобные строки).
Практический пример: сохранение кортежа
function tuple<T extends readonly unknown[]>(...items: T): T {
return items;
}
const withoutConst = tuple(1, "a", true);
// T = (string | number | boolean)[] без as const
function tupleConst<const T extends readonly unknown[]>(...items: T): T {
return items;
}
const withConst = tupleConst(1, "a", true);
// T = readonly [1, "a", true] — точные литералы и readonly
В TypeScript 5.0 появилась модификатор const прямо у type parameter (<const T>), который автоматически применяет поведение as const ко всем аргументам, выведенным в этот параметр, — не нужно расставлять as const на каждом вызове вручную.
Зачем это нужно на практике
- Литеральные union-типы вместо
string— точные автокомплиты и защита от опечаток. readonlyмассивы и объекты — компилятор не даст случайно мутировать конфиг.- Точный вывод кортежей вместо массивов произвольной длины — полезно для builder-функций, роутеров, схем валидации.
- Совместим с
satisfies, который часто используют вместе сas const, чтобы проверить форму объекта, не теряя литеральные типы.
Ограничения
as const— это только компиляторная подсказка, на рантайм она не влияет.- Делает объект/массив
readonlyна уровне типов — попытка мутации будет ошибкой компиляции, но не рантайма. <const T>у generic-параметра работает начиная с TypeScript 5.0.- Не решает проблему widening для
let-переменных внутри функции — эффект действует только в точке, где применена ассерция.
Что хочет услышать интервьюер
Объяснение widening (расширения) литеральных типов и того, как const assertion его отключает
Понимание разницы между `as const` на значении и модификатором `const` у type parameter (`<const T>`, TS 5.0+)
Пример, где generic-функция без const assertion теряет точность типа (string вместо литерала, number[] вместо кортежа)
Знание, что const assertion делает объекты/массивы readonly только на уровне типов, без влияния на рантайм
Связь с практическими кейсами: builder-функции, конфиги, роутинг, action-типы редьюсера
Пример: Widening без и с as const
function identity<T>(value: T): T {
return value;
}
const withoutConst = identity("admin");
// тип: string
const withConst = identity("admin" as const);
// тип: "admin"
Пример: const type parameter (TypeScript 5.0+)
function tupleConst<const T extends readonly unknown[]>(...items: T): T {
return items;
}
const result = tupleConst(1, "a", true);
// T = readonly [1, "a", true]
Пример: as const + satisfies для конфигов
const routes = {
home: "/",
profile: "/profile",
} as const satisfies Record<string, string>;
type RouteKey = keyof typeof routes;
// "home" | "profile"
Типичные ошибки
Путают `as const` с обычным type assertion `as SomeType` — считают, что это принудительное приведение типа, а не сужение вывода
Не понимают, зачем нужен readonly, и пытаются мутировать массив/объект после `as const`, получая ошибку компиляции
Забывают, что без `as const` generic-функция выведет расширенный тип (`string`, `number[]`) вместо литерала или кортежа
Считают, что const assertion как-то влияет на рантайм-поведение или производительность
Не знают о синтаксисе `<const T>` у generic-параметров (TS 5.0+) и вручную расставляют `as const` на каждый вызов


